#include "BlockPuzzle.h"

void BlockPuzzle::Init()
{
	mScore = 0;

	mMainMap.Erase();
	mMainMap.LoadFile(gMainMapFile);

	PickTetrad(true);
}

void BlockPuzzle::Run()
{
	Init();
	// check collisions once to place the maps in the correct locations
	CheckCollisions();
	// render once to display the current map to the screen
	Render();

	int count = 0;

	// Enter the main loop
	while (mUserInput != 27)// 27 is the ASCII code for escape
	{
		while (1)
		{
			if (kbhit())
			{
				mUserInput = getch();
				Update();
				break;
			}
			else
			{
				if (count <= TIME_PER_AUTOMOVE)
				{
					Sleep(TIME_PER_ITERATION);
					count += TIME_PER_ITERATION;
				}
				else
				{
					count = 0;
					mUserInput = AUTO_MOVE;
					Update();
					break;
				}
			}
		}

		Render();
	}

	Quit();
}

void BlockPuzzle::Update()
{
	// switch based on the input command
	switch(mUserInput)
	{
#ifdef DEBUG_MODE
	case 'w':
		mLastInput = INPUT_MOVEUP;
		--mCursorY;
		break;
#endif

	case 's':
		mLastInput = INPUT_MOVEDOWN;
		++mCursorY;
		break;

	case 'a':
		mLastInput = INPUT_MOVELEFT;
		--mCursorX;
		break;
		
	case 'd':
		mLastInput = INPUT_MOVERIGHT;
		++mCursorX;
		break;

	case 'e':
		mLastInput = INPUT_ROTATECW;
		mTetrad.RotateCW();
		break;

	case 'q':
		mLastInput = INPUT_ROTATECCW;
		mTetrad.RotateCCW();
		break;

	case AUTO_MOVE: // special case when the user hasn't input anything and the block auto-moves
		mLastInput = INPUT_NONE;
		++mCursorY;
		break;

	default:
		// unknown command. Print an error here?
		break;
	}

	CheckCollisions();
}

void BlockPuzzle::Render()
{
	mMainMap.Draw(mMainX, mMainY);
	mTetrad.Draw(mCursorX, mCursorY);

	// draw the current store, credits, etc.
	COORD point = {TITLE_X, TITLE_Y};
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), point);
	printf("'Blockpuzzle' by Chris Dickinson");
	
	point.Y++;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), point);
	printf("================================");

	point.Y++;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), point);
	printf("Score: %8i", mScore);

	point.Y+=2;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), point);
	printf("Next Block:");

	for (int i = 0; i < NUM_LINES_TO_CLEAR; ++i)
	{
		COORD p = {NEXT_TETRAD_X, NEXT_TETRAD_Y};
		p.Y += i;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), p);
		printf("          ");
	}

	// clear the general area of *'s
	mNextTetrad.Draw(NEXT_TETRAD_X, NEXT_TETRAD_Y);
}

void BlockPuzzle::Quit()
{
}

bool BlockPuzzle::CheckCollisions(bool skipPermenance)
{
	bool undo = false;
	bool hitBottom = false;

	// limit B's movement based on the size of A.
	// check left side
	if (mCursorX < mMainX)
		mCursorX = mMainX;

	// check right side
	int x = mMainX + mMainMap.GetWidth() - mTetrad.GetWidth();
	if (mCursorX > x)
		mCursorX = x;

	// check top
	if (mCursorY < mMainY)
		mCursorY = mMainY;

	// check bottom
	int y = mMainY + mMainMap.GetHeight() - mTetrad.GetHeight();
	if (mCursorY > y)
	{
		undo = true;
		hitBottom = true;
		mCursorY = y;
	}

	// check collisions between the maps
	
	// get the elements in B, and check if any of them are overlapping
	// if so, we undo the last input, whatever it was.

	

	// so we don't need to call these over and over
	int BHeight = mTetrad.GetHeight();
	int BWidth = mTetrad.GetWidth();

	// since B is smaller, loop through all of it's elements, checking them against the corresponding
	// location on A
	for(int i = 0; i < BHeight; ++i)
	{
		for (int j = 0; j < BWidth; ++j)
		{
			char BElem = mTetrad.GetElement(j, i);
			char AElem = mMainMap.GetElement(mCursorX - mMainX + j, mCursorY - mMainY + i);
			
			// is the element in A a lowercase character?
			if (AElem == PERMANENT_ELEMENT)
			{
				// is the element in B a *?
				if (BElem == TETRAD_ELEMENT)
				{
					// we have a collision, undo the move.
					undo = true;
					break;
				}
			}
		}
	}

	// we found a collision, so we need to undo what we just did
	// back when we responded to input
	if (undo)
	{
		if (!skipPermenance)
		{
			// switch based on the last input
			switch(mLastInput)
			{
			case INPUT_MOVEUP:
				++mCursorY;
				break;

			case INPUT_MOVELEFT:
				++mCursorX;
				break;
		
			case INPUT_MOVERIGHT:
				--mCursorX;
				break;

			case INPUT_ROTATECW:
				mTetrad.RotateCCW();
				break;

			case INPUT_ROTATECCW:
				mTetrad.RotateCW();
				break;

			case INPUT_MOVEDOWN:
			case INPUT_NONE:
				// the block moved down and caused a collision
				// cause it to become permanent here
				if (!hitBottom)
					--mCursorY;
				MakeTetradPermanent();
				break;

			default:
				// unknown command. Print an error here?
				break;
			}
		}

		return true;
	}

	return false;
}

void BlockPuzzle::PickTetrad(bool initialized)
{
	// set the cursor position
	mCursorY = 0;
	mCursorX = mMainMap.GetWidth() / 2 + mMainX - 1;

	mTetrad.Erase();

	if (!initialized)
	{
		// copy mNextTetrad into mTetrad
		mTetrad.Copy(mNextTetrad);
	}
	else
	{
		// we're initializing the game
		// create a new Tetrad for mTetrad.
		int r = rand() % NUM_TETRADS;

		mTetrad.LoadFile(gTetradFiles[r]);
	}

	// create the next Tetrad
	int r = rand() % NUM_TETRADS;

	mNextTetrad.Erase();

	mNextTetrad.LoadFile(gTetradFiles[r]);
	//mTetrad.LoadFile(gTetradFiles[1]);

	// check collisions on the current tetrad
	CheckCollisions(true);
}

void BlockPuzzle::MakeTetradPermanent()
{
	// copy the data from the tetrad into the main map
	
	// loop through the tetrad's components, copying them into the
	// relevant position in the main map

	// so we don't need to call these over and over
	int BHeight = mTetrad.GetHeight();
	int BWidth = mTetrad.GetWidth();

	// since B is smaller, loop through all of it's elements, checking them against the corresponding
	// location on A
	for(int i = 0; i < BHeight; ++i)
	{
		for (int j = 0; j < BWidth; ++j)
		{
			if (mTetrad.GetElement(j, i) == TETRAD_ELEMENT)
			{
				int x = mCursorX-mMainX+j;
				int y = mCursorY-mMainY+i;
				mMainMap.SetElement(x, y, PERMANENT_ELEMENT);
			}
		}
	}

	// now that a new Tetrad has been added, check if we need to destroy any rows
	DestroyFullRows();

	mTetrad.Erase();

	PickTetrad();

	// check collisions. If true, there is no room left and the game is lost
	if (CheckCollisions(true))
	{
		// end game
		RestartGame();
	}

}

void BlockPuzzle::RestartGame()
{
	Init();
}

void BlockPuzzle::DestroyFullRows()
{
	// loop through the Main Map checking if a row is full of permanent elements, 
	// and delete them

	for (int i = 0; i < mMainMap.GetHeight(); ++i)
	{
		bool fullRow = true;
		for (int j = 0; j < mMainMap.GetWidth(); ++j)
		{
			if (mMainMap.GetElement(j, i) == EMPTY_ELEMENT)
				fullRow = false; // if there's an empty element, it's not full!
		}

		if (fullRow)
		{
			// make this row empty
			for (int j = 0; j < mMainMap.GetWidth(); ++j)
			{
				mMainMap.SetElement(j, i, EMPTY_ELEMENT);
			}

			// increase the score
			mScore += SCORE_PER_ROW;

			// now that we've deleted this row, shift everything down
			// copy in reverse order so we don't wipe everything out
			for (int y = i-1; y > 0; --y)
			{
				for (int x = 0; x < mMainMap.GetWidth(); ++x)
				{
					mMainMap.SetElement(x, y+1, mMainMap.GetElement(x, y));
				}
			}
		}
	}
}